﻿using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Sgr.DataCategories.Stores
{
    /// <summary>
    /// 异步本地缓存，适用于全量加载、定期刷新的只读数据（如字典、配置、行政区划等）。
    /// 支持缓存过期、后台预刷新、手动刷新、清空等操作。
    /// </summary>
    /// <typeparam name="T">缓存值类型，通常为 IReadOnlyList&lt;TItem&gt; 或类似集合</typeparam>
    public class GenericLocalCache<T> : IDisposable where T : class
    {
        private readonly SemaphoreSlim _lock = new(1, 1);
        private volatile bool _disposed;
        private readonly TimeSpan _cacheExpiration;
        private readonly TimeSpan _backgroundRefreshTimeout;
        private readonly ILogger? _logger;
        private readonly string _cacheName;

        // 0 = 无刷新进行，1 = 刷新中
        private int _refreshing;

        private sealed class CacheEntry
        {
            public CacheEntry(T? value, DateTime expiration)
            {
                Value = value;
                ExpirationTime = expiration;
            }

            public T? Value { get; }
            public DateTime ExpirationTime { get; }
        }

        private CacheEntry _cache;

        /// <summary>
        /// 初始化缓存实例。
        /// </summary>
        /// <param name="cacheName">缓存名称，用于日志区分（如 "AdmDiv"）</param>
        /// <param name="expirationTimeoutSeconds">过期时间（秒）</param>
        /// <param name="refreshTimeoutSeconds">后台刷新超时（秒）</param>
        /// <param name="logger">日志记录器</param>
        public GenericLocalCache(
            string cacheName,
            long expirationTimeoutSeconds = 7200,
            long refreshTimeoutSeconds = 60,
            ILogger? logger = null)
        {
            _cacheName = cacheName ?? throw new ArgumentNullException(nameof(cacheName));

            _logger = logger;
            _cacheExpiration = TimeSpan.FromSeconds(expirationTimeoutSeconds);
            _backgroundRefreshTimeout = TimeSpan.FromSeconds(refreshTimeoutSeconds);

            // 初始化为立即过期，触发首次加载
            _cache = new CacheEntry(null, DateTime.UtcNow);
        }

        /// <summary>
        /// 缓存是否已过期。
        /// </summary>
        public bool IsExpired => DateTime.UtcNow >= _cache.ExpirationTime;

        /// <summary>
        /// 获取缓存过期时间。
        /// </summary>
        public DateTime ExpirationTime => _cache.ExpirationTime;

        /// <summary>
        /// 获取缓存值，若未命中则通过 valueFactory 加载。
        /// </summary>
        public async Task<T> GetOrAddAsync(
            Func<CancellationToken, Task<T>> valueFactory,
            CancellationToken cancellationToken = default)
        {
            CheckDisposed();
            Check.NotNull(valueFactory, nameof(valueFactory));

            var snapshot = _cache;
            var now = DateTime.UtcNow;

            if (now < snapshot.ExpirationTime && snapshot.Value is not null)
            {
                _logger?.LogDebug("{CacheName} 缓存命中", _cacheName);

                var refreshThresholdSeconds = Math.Min(30, Math.Max(1, _cacheExpiration.TotalSeconds * 0.1));
                if (snapshot.ExpirationTime - now <= TimeSpan.FromSeconds(refreshThresholdSeconds))
                {
                    TryStartBackgroundRefresh(valueFactory);
                }
                return snapshot.Value;
            }

            _logger?.LogDebug("{CacheName} 缓存未命中，正在加载...", _cacheName);

            await _lock.WaitAsync(cancellationToken).ConfigureAwait(false);
            try
            {
                // 双重检查
                snapshot = _cache;
                now = DateTime.UtcNow;
                if (now < snapshot.ExpirationTime && snapshot.Value is not null)
                {
                    _logger?.LogDebug("{CacheName} 等待期间已由其他线程加载完成", _cacheName);
                    return snapshot.Value;
                }

                var value = await valueFactory(cancellationToken).ConfigureAwait(false)
                            ?? throw new InvalidOperationException($"{_cacheName} valueFactory returned null");

                _cache = new CacheEntry(value, DateTime.UtcNow.Add(_cacheExpiration));
                _logger?.LogInformation("{CacheName} 已刷新缓存", _cacheName);
                return value;
            }
            finally
            {
                _lock.Release();
            }
        }

        private void TryStartBackgroundRefresh(Func<CancellationToken, Task<T>> factory)
        {
            if (_disposed) return;
            if (Interlocked.CompareExchange(ref _refreshing, 1, 0) == 0)
            {
                _logger?.LogDebug("{CacheName} 触发后台刷新", _cacheName);
                _ = Task.Run(() => RefreshInBackgroundAsync(factory));
            }
        }

        private async Task RefreshInBackgroundAsync(Func<CancellationToken, Task<T>> factory)
        {
            if (_disposed)
            {
                Volatile.Write(ref _refreshing, 0);
                return;
            }

            using var cts = new CancellationTokenSource(_backgroundRefreshTimeout);
            try
            {
                var value = await factory(cts.Token).ConfigureAwait(false)
                            ?? throw new InvalidOperationException($"{_cacheName} background refresh returned null");

                if (_disposed) return;

                await _lock.WaitAsync(cts.Token).ConfigureAwait(false);
                try
                {
                    if (_disposed) return;
                    _cache = new CacheEntry(value, DateTime.UtcNow.Add(_cacheExpiration));
                    _logger?.LogInformation("{CacheName} 后台刷新完成", _cacheName);
                }
                finally
                {
                    _lock.Release();
                }
            }
            catch (OperationCanceledException) when (!_disposed)
            {
                _logger?.LogDebug("{CacheName} 后台刷新被取消", _cacheName);
            }
            catch (Exception ex) when (!_disposed)
            {
                _logger?.LogError(ex, "{CacheName} 后台刷新时发生错误（忽略并保留旧缓存）", _cacheName);
            }
            finally
            {
                Volatile.Write(ref _refreshing, 0);
            }
        }

        /// <summary>
        /// 手动刷新缓存。
        /// </summary>
        public async Task RefreshAsync(T value, CancellationToken cancellationToken = default)
        {
            CheckDisposed();
            ArgumentNullException.ThrowIfNull(value);

            await _lock.WaitAsync(cancellationToken).ConfigureAwait(false);
            try
            {
                _cache = new CacheEntry(value, DateTime.UtcNow.Add(_cacheExpiration));
                Volatile.Write(ref _refreshing, 0);
                _logger?.LogInformation("{CacheName} 已手动刷新", _cacheName);
            }
            finally
            {
                _lock.Release();
            }
        }

        /// <summary>
        /// 清空缓存。
        /// </summary>
        public async Task ClearAsync(CancellationToken cancellationToken = default)
        {
            CheckDisposed();

            await _lock.WaitAsync(cancellationToken).ConfigureAwait(false);
            try
            {
                _cache = new CacheEntry(null, DateTime.MinValue);
                Volatile.Write(ref _refreshing, 0);
                _logger?.LogInformation("{CacheName} 已清空缓存", _cacheName);
            }
            finally
            {
                _lock.Release();
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed) return;
            if (disposing)
            {
                _lock.Dispose();
                _cache = new CacheEntry(null, DateTime.MinValue);
            }
            _disposed = true;
        }

        private void CheckDisposed()
        {
            ObjectDisposedException.ThrowIf(_disposed, this);
        }
    }
}